/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.worlds.object;

import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Stroke;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.worlds.WorldsBoxDef;
import net.worlds.WorldsException;
import net.worlds.WorldsJointDef;
import net.worlds.WorldsSettings;
import net.worlds.Main;
import net.worlds.PhysicsController;
import net.worlds.PropChangeHandler;
import net.worlds.Settings;
import net.worlds.Util;
import net.worlds.lua.Event;
import net.worlds.ui.UI;
import org.jbox2d.collision.shapes.CircleDef;
import org.jbox2d.collision.MassData;
import org.jbox2d.collision.shapes.PolygonDef;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.collision.shapes.ShapeDef;
import org.jbox2d.common.Mat22;
import org.jbox2d.common.Vec2D;
import org.jbox2d.common.XForm;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import se.krka.kahlua.vm.JavaFunction;
import se.krka.kahlua.vm.LuaCallFrame;

/**
 *
 * @author Jacob Morgan
 */
public abstract class Part extends GameObject implements Prop{
    protected Body body;
    protected Shape shape;
    protected BodyDef bd;
    protected ShapeDef sd;
    protected ArrayList<Joint> joints = new ArrayList<Joint>(WorldsSettings.MAX_JOINTS);
    protected ArrayList<WorldsJointDef> jointDefs = new ArrayList<WorldsJointDef>(WorldsSettings.MAX_JOINTS);
    protected double oldRotation;

    protected int bodyID = -1;

    public Vec2D localOffset = new Vec2D(0, 0);
    public float rotationDiff;

    private Vec2D targetPoint;
    private Vec2D startPosition;
    private float movePosition = 0;
    private long moveFrame;
    public long stepFrame;
    public boolean isMoving = false;
    public Vec2D vSeconds = null;

    public BufferedImage texture;
    public boolean showSelf = false;
    protected String label;
    protected Vec2D distanceFromMouse;
    protected float maxForce = 0;

    public boolean inWater = false;
    public boolean inWaterNow = false;

    private ArrayList<PhysicsController> physicsControllers = new ArrayList<PhysicsController>(4);

    public Vec2D smoothedPosition;
    public float smoothedAngle;

    public boolean characterCollide = true;

    public boolean noFriction = false;
    public boolean noCollide = false;

    public boolean fixedRotation = false;

    public boolean isCharacter = false;
    public boolean isRemoteCharacter = false;

    public boolean isDestroyable = true;
    public int destroyedLevel = 0;
    private long destroyedFrame = 0;
    public boolean isFinished = true;
    public int explodedBy = -1;
    private final java.awt.Color transparent = new java.awt.Color(0, 0, 0, 0);
    protected final float dash[] = { 10.0f };
    protected final Stroke dashedStroke = new BasicStroke(3.0f, BasicStroke.CAP_BUTT,
        BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
    protected Stroke defaultStroke;

    public Part(String type) throws WorldsException
    {
        super(type);
        bd = new BodyDef();
        bd.userData = this;
        addPropChangeHandler("Layer", new PropChangeHandler()
        {
            @Override
            public void handle(final Object oldValue, final Object value) throws WorldsException {
                if((Integer) value < 0 || (Integer) value > 2)
                {
                    System.err.println("Layer must be >= 0 and <= 2");
                    setProp("Layer", oldValue, false);
                }
                else if(inWorld)
                {
                    world.changeLayer(instance, (Integer) oldValue, (Integer) value);
                    body.resetFiltering();
                }
            }

        });
        addPropChangeHandler("Collidable", new PropChangeHandler()
        {
            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                boolean v = (Boolean) value;
                if(!v)
                    addAction(new Runnable()
                    {
                        public void run() {
                            getShape().m_isSensor = true;
                            body.resetFiltering();
                        }
                    });
                else
                    addAction(new Runnable()
                    {
                        public void run() {
                            getShape().m_isSensor = false;
                        }
                    });
                    if(inWorld)
                        body.resetFiltering();
            }

        });
        addPropChangeHandler("CharacterCollide", new PropChangeHandler()
        {
            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                boolean v = (Boolean) value;
                if(!v)
                    addAction(new Runnable()
                    {
                        public void run() {
                            characterCollide = false;
                            body.resetFiltering();
                        }
                    });
                else
                    addAction(new Runnable()
                    {
                        public void run() {
                            characterCollide = true;
                        }
                    });
                    if(inWorld)
                        body.resetFiltering();
            }

        });
        addPropChangeHandler("Position", new PropChangeHandler()
        {
            @Override
            public void handle(final Object oldValue, final Object value) throws WorldsException {
                if(inWorld)
                {
                    Vec2D pos = (Vec2D) value;
                    XForm xf = new XForm();
                    xf.position = pos;
                    xf.R = new Mat22(body.getAngle());
                    if(localOffset.x == 0 && localOffset.y == 0)
                        body.setXForm(pos, body.getAngle());
                    else
                        body.setXForm(XForm.mul(xf, localOffset.negate()), body.getAngle());
                    body.wakeUp();
                    wakeUpConnected();
                    if(Main.getView() != null && world == Main.getGame().getCurrentWorld() && Main.getView().followObject == Part.this)
                    {
                        Main.getView().setCamPosition(getPosition(), false);
                    }
                }
                else
                    bd.position = ((Vec2D)value);
            }

        });
        addPropChangeHandler("Rotation", new PropChangeHandler()
        {

            @Override
            public void handle(final Object oldValue, final Object value) {
                if(inWorld)
                {
                    body.setXForm(body.getPosition(), (float) Math.toRadians((Float) value));
                    body.wakeUp();
                }
            }

        });
        addPropChangeHandler("Velocity", new PropChangeHandler()
        {

            @Override
            public void handle(final Object oldValue, final Object value) {
                addAction(new Runnable()
                {

                    public void run() {
                        body.setLinearVelocity(((Vec2D)value));
                        body.wakeUp();
                    }

                });
            }

        });
        addPropChangeHandler("AngularVelocity", new PropChangeHandler()
        {

            @Override
            public void handle(final Object oldValue, final Object value) {
                addAction(new Runnable()
                {

                    public void run() {
                        body.setAngularVelocity((Float) value);
                        body.wakeUp();
                    }

                });
            }

        });
        addPropChangeHandler("Bounciness", new PropChangeHandler()
        {

            @Override
            public void handle(final Object oldValue, final Object value) {
                addAction(new Runnable()
                {

                    public void run() {
                        shape.setRestitution((Float) value);
                    }

                });
            }

        });
        addPropChangeHandler("Fixed", new PropChangeHandler()
        {

            @Override
            public void handle(final Object oldValue, final Object value) {
                if(inWorld)
                {
                    if(value != oldValue)
                    {
                        if((Boolean) value)
                            body.m_numFixed++;
                        else
                            body.m_numFixed--;
                    }
                    body.setFixed((Boolean) value, false, true);
                    body.wakeUp();
                }
            }
        });
        addPropChangeHandler("Mass", new PropChangeHandler()
        {

            @Override
            public void handle(Object oldValue, final Object value) throws WorldsException {
                addAction(new Runnable()
                {
                    public void run() {
                        if (!body.m_isFixed)
                        {
                             float m = (Float) value;
                            if(m == 0)
                                getBody().setMassFromShapes();
                            else
                            {
                                MassData md = new MassData();
                                if(sd instanceof CircleDef)
                                {
                                    md.I = md.I = md.mass * (0.5f * ((CircleDef)sd).radius * ((CircleDef)sd).radius + Vec2D.dot(((CircleDef)sd).localPosition, ((CircleDef)sd).localPosition));
                                }
                                else if(sd instanceof PolygonDef)
                                {
                                    md.I = (float) (body.m_I * (m / body.getMass()));
                                }
                                md.center = getBody().getLocalCenter();
                                md.mass = (float) m;
                                getBody().setMass(md);
                            }
                            getBody().customMass = m;
                        }
                    }
                });
            }
        });
        //x_;
        addFunction("breakJoints", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                Main.runInMainThread(new Runnable()
                {
                    public void run()
                    {
                        try {
                            breakJoints();
                        } catch (WorldsException ex) {
                            throw new RuntimeException(ex);
                        }
                    }
                });
                return 0;
            }
        });
        addFunction("setIsCharacter", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                boolean v = (Boolean) callFrame.get(1);
                isCharacter = v;
                return 0;
            }
        });
        addFunction("moveToFront", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                try {
                    UI.moveToFront((Part) instance);
                } catch (WorldsException ex) {
                    throw new RuntimeException(ex);
                }
                return 0;
            }
        });
        addFunction("moveToBack", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                try {
                    UI.moveToBack((Part) instance);
                } catch (WorldsException ex) {
                    throw new RuntimeException(ex);
                }
                return 0;
            }
        });
        addFunction("setTargetPoint", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                Vec2D v = (Vec2D)callFrame.get(1);
                setTargetPoint(v);
                return 0;
            }
        });
        addFunction("setBody", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                double d = (Double) callFrame.get(1);
                bodyID = (int) d;
                return 0;
            }
        });
        addFunction("setAngularDamping", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
               if(nArguments != 2 || !Util.isDouble(callFrame.get(1).toString()))
                    throw new RuntimeException("setAngularDamping() requires an integer argument");
                double d = (Double) callFrame.get(1);
                bd.angularDamping = (float) d;
                return 0;
            }
        });
        addFunction("setLinearDamping", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
               if(nArguments != 2 || !Util.isDouble(callFrame.get(1).toString()))
                    throw new RuntimeException("setLinearDamping() requires an integer argument");
                double d = (Double) callFrame.get(1);
                bd.linearDamping = (float) d;
                return 0;
            }
        });
        addPropChangeHandler("MaxForce", new PropChangeHandler()
        {

            @Override
            public void handle(final Object oldValue, final Object value) throws WorldsException {
                maxForce = (Float) value;
            }
        });
        addPropChangeHandler("Friction", new PropChangeHandler()
        {

            public void handle(Object oldValue, final Object value) throws WorldsException {
                addAction(new Runnable()
                {
                    public void run() {
                        if((Float)value >= 0)
                            shape.setFriction((Float) value);
                    }
                });
                sd.friction = (Float)value;
            }
        });
        addPropChangeHandler("Transparency", new PropChangeHandler()
        {
            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                    float v = (Float) value;
                    if(v < 0)
                        setProp("Transparency", 0.0f, false);
                    else if(v > 1)
                        setProp("Transparency", 1.0f, false);
            }
        });
        addEvent("Collided", new Event(), "collidePart (GameObject)");
        addEvent("TerrainCollided", new Event(), "none");
        addEvent("Clicked", new Event(), "player (GameObject), button (integer)");
        addEvent("Dragged", new Event(), "player (GameObject), button (integer)");
        addEvent("OutOfBounds", new Event(), "none");
        if(Main.isClient() && !Main.loading)
            isFinished = false;
        /*moveController = new PhysicsController()
        {
            @Override
            public void run() {*/
    }
    @Override
    public Object getProp(String name) throws WorldsException
    {
        if(name.equals("Position") && inWorld)
            return getPosition();
        else if(name.equals("Mass") && inWorld)
            return body.getMass();
        else if(name.equals("Velocity") && inWorld)
            return new Vec2D(body.getLinearVelocity());
        else if(name.equals("AngularVelocity") && inWorld)
            return body.getAngularVelocity();
        else if(name.equals("Rotation") && inWorld)
            return (float) Math.toDegrees(body.getAngle() + rotationDiff);
        else
            return super.getProp(name);
    }
    public void addController(PhysicsController c)
    {
        if(!physicsControllers.contains(c))
            physicsControllers.add(c);
    }
    public void removeController(PhysicsController c)
    {
        physicsControllers.remove(c);
    }
    public boolean hasController(PhysicsController c)
    {
        return physicsControllers.contains(c);
    }
    public void processMove()
    {
        if(!isMoving || moveFrame == world.frame)
            return;
        Vec2D d = targetPoint.sub(startPosition);
        movePosition += 1/6f;
        if(movePosition >= 1.0)
        {
            isMoving = false;
            Main.addStepTask(new Runnable() {
                public void run() {
                    try {
                        setTempFixed(false);
                    } catch (WorldsException ex) {
                        ex.printStackTrace();
                    }
                }
            });
        }
        else
        {
            Vec2D v = d.mul(movePosition).sub(d.mul(movePosition-1/8f));
            try {
                body.setLinearVelocity(d);
                setPosition(startPosition.add(d.mul(movePosition)));
                moveFrame = world.frame;
            } catch (WorldsException ex) {
                ex.printStackTrace();
            }
        }
    }
    public void processControllers()
    {
        try {
            if (getParent() instanceof Character && getOldPosition().x != 0) {
            }
            for (PhysicsController c : physicsControllers) {
                c.run();
            }
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }
    }
    public Vec2D getPosition()
    {
        if(inWorld)
        {
            if(localOffset.x == 0.0 && localOffset.y == 0.0)
                return new Vec2D(body.getPosition());
            else
                return new Vec2D(body.getWorldPoint(localOffset));
        }
        else
            try {
            return getVec2DProp("Position");
        } catch (WorldsException ex) {
            ex.printStackTrace();
            return null;
        }
    }
    public Vec2D getDrawPosition()
    {
        if(inWorld && this == Main.getView().followObject)
            return Main.getView().followObjectPos;
        else
            return getPosition();
    }
    public Vec2D getOldPosition()
    {
        if(localOffset.x == 0.0 && localOffset.y == 0.0)
            return new Vec2D(body.getOldPosition());
        else
        {
            XForm xf = new XForm();
            xf.position = body.getOldPosition();
            xf.R =  new Mat22(body.getAngle());
            return new Vec2D(XForm.mul(xf, localOffset));
        }
    }
    public Vec2D getVelocity()
    {
        return new Vec2D(body.getLinearVelocity());
    }
    public void setPosition(Vec2D vec) throws WorldsException
    {
        body.setXForm(vec, body.getAngle());
        setProp("Position", vec, false);
    }
    public void setShapePosition(Vec2D vec)
    {
        if(this instanceof Box || this instanceof Triangle)
        {
            Vec2D d = vec.sub(getPosition()).rotate(-body.getAngle() - rotationDiff);
            //Vec2 d = new Vec2D(0, 5).toVec2();
            PolygonDef newsd = ((PolygonDef) sd).translate(d);
            changeShape(newsd);
        }
    }
    @Override
    public void addToWorld() throws WorldsException
    {
        //new Throwable().printStackTrace();
        rebuild();
        if (Main.isClient() && getParent() instanceof Character)
        {
            isRemoteCharacter = true;
        }
        super.addToWorld();
    }
    public void rebuild() throws WorldsException
    {
        Util.checkThread();
        float oldMass = 0;
        if(body != null)
        {
            oldMass = body.getMass();
            world.removeBody(body);
        }
        bd.position = getVec2DProp("Position");
        bd.angle = (float) Math.toRadians(getFloatProp("Rotation"));
        if(Settings.getBoolean("UsePerfectCollisions") || (body != null && body.isBullet()))
            bd.isBullet = true;
        else
            bd.isBullet = false;
        sd.restitution = (float) getFloatProp("Bounciness");
        sd.friction = (float) getFloatProp("Friction");
        if(bodyID == -1 || bodyID == getID())
            body = world.addBody(bd);
        else
        {
            body = ((Part)world.game.getByID(bodyID)).getBody();

            XForm xf = new XForm();
            xf.R = new Mat22(bd.angle);
            xf.position = bd.position;
            
            ShapeDef newsd = null;
            if(this instanceof Box || this instanceof Triangle)
            {
                newsd = new PolygonDef();
                newsd.density = 1f;
                newsd.friction = 0.8f;
                newsd.restitution = 0.4f;
                for(Vec2D v : ((PolygonDef)sd).vertices)
                {
                    ((PolygonDef)newsd).addVertex(body.getLocalPoint(XForm.mul(xf, v)));
                }
                localOffset = new Vec2D(PolygonShape.computeCentroid(((PolygonDef)newsd).vertices));
            }
            else if(this instanceof Circle)
            {
                newsd = new CircleDef();
                newsd.density = 1f;
                newsd.friction = 0.8f;
                newsd.restitution = 0.4f;
                ((CircleDef)newsd).radius = ((CircleDef)sd).radius;
                localOffset = new Vec2D(body.getLocalPoint(bd.position));
                ((CircleDef)newsd).localPosition = localOffset;
            }
            rotationDiff = bd.angle - body.getAngle();

            sd = newsd;
        }
        if(getBooleanProp("Fixed"))
        {
            sd.density = 0;
            body.m_isFixed = true;
        }
        body.addPart(this);
        body.setUserData(this);
        shape = body.createShape(sd);
        shape.setUserData(this);
        body.setMassFromShapes();
        if(oldMass != 0 && oldMass != body.getMass())
            body.setMass(oldMass);
        if(getBooleanProp("Fixed"))
            body.setFixed(true, false, true);
        for (Joint j : (ArrayList<Joint>) joints.clone()) {
            if(j instanceof Hinge)
                j.createJoint();
        }
        List<GameObject> children = getChildren();
        synchronized(children)
        {
            for(GameObject c : children)
            {
                if(c instanceof FixedPosition)
                {
                    ((FixedPosition)c).started = false;
                    ((FixedPosition)c).start(this);
                }
            }
        }
    }
    public Shape rebuildShape() throws WorldsException
    {
        shape = changeShape(sd);
        return shape;
    }
    public Shape changeShape(ShapeDef newsd)
    {
        if(body.m_isFixed || body.m_isAlwaysFixed)
            newsd.density = 0;
        Shape newShape = body.createShape(newsd);

        Vec2D centroid = null;
        if(this instanceof Box || this instanceof Triangle)
        {
            centroid = ((PolygonShape)newShape).getCentroid();
            localOffset = new Vec2D(centroid);
        }
        else if(this instanceof Circle)
        {
            localOffset = new Vec2D(((CircleDef)newsd).localPosition);
        }

        rotationDiff = getBody().getAngle() + rotationDiff - body.getAngle();
        newShape.setUserData(this);

        body.setMassFromShapes();

        body.destroyShape(shape);
        body.setMassFromShapes();

        setShapeDef(newsd);
        setShape(newShape);
        return newShape;
    }
    protected double scaleDimensionForScreen(int origDimension, double entityDimension)
    {
        return (Main.getView().scaleFactor * entityDimension) / origDimension;
    }
    public void translate(Vec2D tvec) throws WorldsException
    {
        setProp("Position", getPosition().add(tvec), true);
    }
    public void setTempFixed(final boolean v) throws WorldsException
    {
        addAction (new Runnable()
        {
            public void run() {
                try {
                    if (!getBooleanProp("Fixed") && !body.m_isAlwaysFixed) {
                        body.setFixed(v, true, false);
                    }
                } catch (WorldsException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }
    public void setTempNoCollide(boolean v)
    {
        noCollide = v;
        body.resetFiltering();
    }
    public void setFixedRotation(final boolean v) throws WorldsException
    {
        bd.fixedRotation = v;
        if(inWorld)
        {
            if(v != fixedRotation)
            {
                if(v)
                    body.m_numFixedR++;
                else
                    body.m_numFixedR--;
                body.setFixedRotation(v);
                body.wakeUp();
                setTempFixed(true);
                setTempFixed(false);
            }
        }
        fixedRotation = v;
    }
    public void addJoint(Joint j)
    {
        if(!joints.contains(j))
        joints.add(j);
    }
    public void removeJoint(Joint j)
    {
        joints.remove(j);
    }
    public ArrayList<Joint> getJoints()
    {
        return joints;
    }
    public void saveAllJoints() throws WorldsException
    {
        ArrayList<Part> parts = new ArrayList<Part>(joints.size());
        for(Joint j : joints)
        {
            Part part = j.getOtherPart(this);
            if(!parts.contains(part))
            {
                parts.add(part);
                saveJoints(part);
            }
        }
    }
    public void saveJoints(Part p) throws WorldsException
    {
        if(p.jointDefs.size() > 0 && !p.isInWorld())
        {
            jointDefs.addAll(p.jointDefs);
        }
        else
            for(Joint j : p.joints)
            {
                WorldsJointDef d = new WorldsJointDef();
                if(j instanceof Weld)
                    d.type = WorldsJointDef.WELD;
                else if(j instanceof Hinge)
                    d.type = WorldsJointDef.HINGE;
                else if(j instanceof SimpleWeld)
                    d.type = WorldsJointDef.OLDWELD;
                else
                    d.type = WorldsJointDef.ROPE;
                if(j instanceof Rope)
                {
                    d.localAnchor1 = j.getVec2DProp("Anchor1");
                    d.localAnchor2 = j.getVec2DProp("Anchor2");
                }
                else
                    d.localAnchor1 = j.getVec2DProp("Anchor");
                d.part1 = j.part1.getID();
                d.part2 = j.part2.getID();
                d.visible = j.getBooleanProp("Visible");
                if(j instanceof Hinge)
                {
                    d.torque = j.getFloatProp("MotorTorque");
                    d.speed = j.getFloatProp("MotorSpeed");
                    d.friction = j.getFloatProp("Friction");
                }
                //if(!jointDefs.contains(d))
                {
                    jointDefs.add(d);
                }
            }
    }
    public void clearJointDefs()
    {
        jointDefs.clear();
    }
    protected void removeJointDef(WorldsJointDef d)
    {
        jointDefs.remove(d);
    }
    public void makeJoints(Model parent) throws WorldsException
    {
        for(WorldsJointDef d : jointDefs)
        {
            Part part1 = null;
            Part part2 = null;
            List<GameObject> children = parent.getChildren();
            synchronized(children)
            {
                for(GameObject c : children)
                {
                    if(c instanceof Part && (c.originalID == d.part1 || c.getID() == d.part1))
                    {
                        part1 = (Part) c;
                    }
                    else if(c instanceof Part && (c.originalID == d.part2 || c.getID() == d.part2))
                    {
                        part2 = (Part) c;
                    }
                }
            }
            if(part1 == null || part2 == null)
                continue;
            Joint joint;
            if(d.type == WorldsJointDef.WELD)
                joint = (Joint) world.createObject("Weld");
            else if(d.type == WorldsJointDef.HINGE)
                joint = (Joint) world.createObject("Hinge");
            else if(d.type == WorldsJointDef.OLDWELD)
                joint = (Joint) world.createObject("SimpleWeld");
            else
                joint = (Joint) world.createObject("Rope");
            joint.setProp("Part1", part1, true);
            joint.setProp("Part2", part2, true);
            if(d.localAnchor2 == null)
                joint.setProp("Anchor", d.localAnchor1, true);
            else
            {
                joint.setProp("Anchor1", d.localAnchor1, true);
                joint.setProp("Anchor2", d.localAnchor2, true);
            }
            joint.setProp("Visible", d.visible, true);
            if(joint instanceof Hinge)
            {
                joint.setProp("MotorTorque", d.torque, true);
                joint.setProp("MotorSpeed", d.speed, true);
                joint.setProp("Friction", d.friction, true);
            }
            if(part1 == this)
                part2.removeJointDef(d);
            else if(part2 == this)
                part1.removeJointDef(d);
            joint.setProp("Parent", world.getService("JointsService"), true);
        }
        originalID = -1;
        //jointDefs.clear();
    }
    public boolean isConnected(Part p)
    {
        return isConnected(p, null);
    }
    public boolean isConnected(Part p, ArrayList<Part> ignoreParts)
    {
        if(ignoreParts == null)
            ignoreParts = new ArrayList<Part>(WorldsSettings.MAX_OBJECTS);
        for(Joint j : joints)
        {
            Part other = j.getOtherPart(this);
            ignoreParts.add(this);
            if(other == p)
                return true;
            else if(!ignoreParts.contains(other) && other.isConnected(p, ignoreParts))
                return true;
        }
        return false;
    }
    public void setOldRotation()
    {
        oldRotation = Math.toDegrees(body.getAngle());
    }
    public double getOldRotation()
    {
        return oldRotation;
    }
    public double getRotation()
    {
        return Math.toDegrees(body.getAngle());
    }
    public void onCollided(final Part other, final Vec2D velocity)
    {
        try {
            if(other instanceof Tool && other.disabled)
                return;
            if (((other.getIntProp("Layer") == getIntProp("Layer")) || (other instanceof Water || this instanceof Water)) && other.getParent() != null) {
                if(!Main.isClient() && getParent() instanceof Character && other instanceof Seat && System.currentTimeMillis() - ((Character)getParent()).leftSeatTime > 750)
                    ((Character)getParent()).setSeat((Seat)other);
                if (getEvent("Collided").hasConnections()) {
                    getEvent("Collided").fire(new Object[]{other, velocity});
                }
            }
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }
    }
    public void onPersisted(Part other)
    {
        
    }
    public void onUncollided(Part other)
    {
        
    }
    @Override
    public void destructor() throws WorldsException
    {
        super.destructor();
        inWorld = false;
        setProp("Position", new Vec2D(body.getPosition()), false);
        setProp("Rotation", (float) Math.toDegrees(body.getAngle()), false);
        saveAllJoints();
        bodyID = -1;
        for(Joint j : (ArrayList<Joint>) joints.clone())
        {
            if(j instanceof Weld || j instanceof Rope)
                world.deleteObject(j, true);
            else
                world.deleteObject(j, false);
            j.removeFromParts();
        }
        if(UI.scaleBox == this)
            UI.scaleBox = null;
        Runnable r = new Runnable()
        {
            public void run() {
                body.destroyShape(shape);
                body.setMassFromShapes();
                body.removePart((Part)instance);
            }
        };
        Main.runInMainThread(r);
    }
    public Body getBody()
    {
        return body;
    }
    protected void setBody(Body b)
    {
        body = b;
    }
    public Shape getShape()
    {
        return shape;
    }
    public void setShape(Shape s)
    {
        shape = s;
    }
    public void setShapeDef(ShapeDef sd)
    {
        this.sd = sd;
    }
    public void setTexture(BufferedImage t)
    {
        /*if(t != null)
        {
            VolatileImage i = Util.getGraphicsConfiguration().createCompatibleVolatileImage(t.getWidth(), t.getHeight(), Transparency.TRANSLUCENT);
            Graphics2D g2 = (Graphics2D) i.getGraphics();
            Composite old = g2.getComposite();
            g2.setColor(transparent);
            g2.setComposite(AlphaComposite.Src);
            g2.fillRect(0, 0, t.getWidth(), t.getHeight());
            g2.setComposite(old);
            g2.drawImage(t, 0, 0, null);
            texture = i;
        }
        else
            texture = null;*/
        texture = t;
    }
    public void setLabel(String t)
    {
        label = t;
    }
    public void breakJoints() throws WorldsException
    {
        for(Joint j : (ArrayList<Joint>) joints.clone())
        {
            j.remove();
        }
    }
    protected void createWeld(Part part1, Part part2, Vec2D pos) throws WorldsException
    {
            Weld w = new Weld();
            w.setProp("Part1",part1, true);
            w.setProp("Part2", part2, true);
            w.setProp("Anchor", pos, true);
            w.setProp("Parent", world.getService("JointsService"), true);
    }
    /*protected ArrayList<Part> subdivide(ArrayList<Part> parts, Vec2D pos, Vec2D origPoint, int level, double ratio, int explosionID) throws BuildismException
    {
        if(level > 8)
            return parts;
        Vec2D local = new Vec2D(body.getLocalPoint(pos.toVec2()));
        Vec2D size = getVec2DProp("Size");
        boolean isY = Math.abs(Math.abs(local.getY()) - size.getY() / 2) < 0.1;
        boolean isX = Math.abs(Math.abs(local.getX()) - size.getX() / 2) < 0.1;
        boolean sub0 = false, sub1 = false;
        Vec2D center0 = null, center1 = null;
        Vec2D next = null;
        double p0X = 0, p1X = 0, p0Y = 0, p1Y = 0;
        if(isY && !isX)
        {
            next = new Vec2D(body.getWorldPoint(new Vec2((float) local.getX(), 0)));
            p0X = size.getX()/2 - Math.abs(local.getX());
            p1X = size.getX() - p0X;
            p0Y = size.getY();
            p1Y = size.getY();
            if(local.getX() > 0)
               center0 = new Vec2D(local.getX()/2 + size.getX()/4, 0);
            else
               center0 = new Vec2D(local.getX()/2 - size.getX()/4, 0);
            if(local.getX() > 0)
               center1 = new Vec2D(local.getX()/2 - size.getX()/4, 0);
            else
               center1 = new Vec2D(local.getX()/2 + size.getX()/4, 0);
        }
        else if(isX && !isY)
        {
            next = new Vec2D(body.getWorldPoint(new Vec2(0, (float) local.getY())));
            p0X = size.getX();
            p1X = size.getX();
            p0Y = size.getY()/2 - Math.abs(local.getY());
            p1Y = size.getY() - p0Y;
            if(local.getY() > 0)
               center0 = new Vec2D(0, local.getY()/2 + size.getY()/4);
            else
               center0 = new Vec2D(0, local.getY()/2 - size.getY()/4);
            if(local.getY() > 0)
               center1 = new Vec2D(0, local.getY()/2 - size.getY()/4);
            else
               center1 = new Vec2D(0, local.getY()/2 + size.getY()/4);
        }
        if(p0X != 0)
        {
            Vec2D worldCenter1 = new Vec2D(body.getWorldPoint(center0.toVec2()));
            Vec2D worldCenter2 = new Vec2D(body.getWorldPoint(center1.toVec2()));
            double d0 = Math.abs(new Vec2D(worldCenter1.getX() - origPoint.getX(), worldCenter1.getY() - origPoint.getY()).getMagnitude());
            double d1 = Math.abs(new Vec2D(worldCenter2.getX() - origPoint.getX(), worldCenter2.getY() - origPoint.getY()).getMagnitude());
            if(Math.abs(d0 - d1) < 0.1 || level == 0)
            {
                sub0 = true;
                sub1 = true;
            }
            else if(d0 > d1)
            {
                sub0 = false;
                sub1 = true;
            }
            else if(d1 > d0)
            {
                sub0 = true;
                sub1 = false;
            }
            Box b = null;
            Box b2 = null;
            if(p0X > 0.5 && p0Y > 0.5)
            {
                b = new Box();
                b.setProp("Position", worldCenter1, true);
                b.setProp("Size", new Vec2D(p0X, p0Y ), true);
                b.setProp("Layer", getProp("Layer"), true);
                b.setProp("Rotation", getProp("Rotation"), true);
                b.setProp("MaxForce", getProp("MaxForce"), true);
                b.setProp("Color", getProp("Color"), true);
                b.setProp("ShowBorder", getProp("ShowBorder"), true);
                b.setProp("Parent", Main.getActiveWorld().getService("World"), true);
                b.getBody().setLinearVelocity(getBody().getLinearVelocity());
                b.getBody().setAngularVelocity(getBody().getAngularVelocity());
                b.getBody().m_explosionID = explosionID;
                b.destroyedLevel = destroyedLevel + 1;
                if(sub0)
                    b.subdivide(parts, next, origPoint, level + 1, ratio, explosionID);
            }
            if(p1X > 0.5 && p1Y > 0.5)
            {
                b2 = new Box();
                b2.setProp("Position", worldCenter2, true);
                b2.setProp("Size", new Vec2D(p1X, p1Y), true);
                b2.setProp("Layer", getProp("Layer"), true);
                b2.setProp("Rotation", getProp("Rotation"), true);
                b2.setProp("MaxForce", getProp("MaxForce"), true);
                b2.setProp("Color", getProp("Color"), true);
                b2.setProp("ShowBorder", getProp("ShowBorder"), true);
                b2.setProp("Parent", Main.getActiveWorld().getService("World"), true);
                b2.getBody().setLinearVelocity(getBody().getLinearVelocity());
                b2.getBody().setAngularVelocity(getBody().getAngularVelocity());
                b2.destroyedLevel = destroyedLevel + 1;
                b2.getBody().m_explosionID = explosionID;
                if(sub1)
                    b2.subdivide(parts, next, origPoint, level + 1, ratio, explosionID);
            }
            Main.getActiveWorld().deleteObject(this);
            parts.remove(this);
            if(!parts.contains(b) && !sub0)
                parts.add(b);
            if(!parts.contains(b2) && !sub1)
                parts.add(b2);
            return parts;
        }
        return parts;
    }*/
    class Pair
    {
        public Part p1;
        public Part p2;
        public Pair(Part p1, Part p2)
        {
            this.p1 = p1;
            this.p2 = p2;
        }
        @Override
        public boolean equals(Object obj)
        {
            if(!(obj instanceof Pair))
                return false;
            Pair p = (Pair) obj;
            if(p1 == p.p1 && p2 == p.p2)
                return true;
            else if(p1 == p.p2 && p2 == p.p1)
                return true;
            else
                return false;
        }

        @Override//b = game.World.Model:clone(); game.World.Model:remove(); sleep(1); b.Parent = game.World; b:makeJoints()

        public int hashCode() {
            int hash = 5;
            hash = 37 * hash + (this.p1 != null ? this.p1.hashCode() : 0);
            hash = 37 * hash + (this.p2 != null ? this.p2.hashCode() : 0);
            return hash;
        }
    }
    public void applyCollisionForce(final Body body, final Vec2D pos, final double magnitude) throws WorldsException
    {
        if(Main.isClient() || destroyedLevel > 3 || !(this instanceof Box) || getBooleanProp("Fixed") || maxForce == 0 || magnitude < maxForce)
            return;
        if(destroyedFrame == world.frame)
            return;
        destroyedFrame = world.frame;
        Main.addStepTask(new Runnable()
        {
            double localDistance = new Vec2D(getPosition().x - pos.x, getPosition().y - pos.y).length();
            public void run() {
                if(body.m_isExplosion)
                {
                    if(explodedBy == body.m_explosionID)
                        return;
                    explodedBy = body.m_explosionID;
                }
                Box box = (Box) instance;
                ArrayList<WorldsBoxDef> parts = new ArrayList<WorldsBoxDef>(128);
                try {
                    WorldsBoxDef myDef = new WorldsBoxDef((Box) instance);
                    parts = myDef.subdivide(parts, pos, pos, 0, 0.5f, explodedBy);
                    if(parts.size() > 0)
                        instance.setProp("Parent", null, true);
                    for(WorldsBoxDef bd : parts)
                    {
                        if(bd == null)
                            continue;
                        Box b = (Box) world.createObject("Box");
                        b.setProp("Position", bd.pos, true);
                        b.setProp("Rotation", (float) Math.toDegrees(bd.angle), true);
                        b.setProp("Size", bd.size.mul(0.98f), true);
                        b.destroyedLevel = destroyedLevel + 1;
                        b.setProp("Layer", bd.getProp("Layer"), true);
                        b.setProp("MaxForce", bd.getProp("MaxForce"), true);
                        b.setProp("Color", bd.getProp("Color"), true);
                        b.setProp("ShowBorder", bd.getProp("ShowBorder"), true);
                        b.setProp("Parent", world.getService("World"), true);
                        b.getBody().setLinearVelocity(bd.velocity);
                        b.getBody().setAngularVelocity((float) bd.angularVelocity);
                /*b2.setProp("Layer", getProp("Layer"), true);
                b2.setProp("Rotation", getProp("Rotation"), true);
                b2.setProp("MaxForce", getProp("MaxForce"), true);
                b2.setProp("Color", getProp("Color"), true);
                b2.setProp("ShowBorder", getProp("ShowBorder"), true);*/
                    }
                    /*ArrayList<Pair> pairs = new ArrayList<Pair>(512);
                    for(Part p : parts)
                    {
                        if(p != null)
                            p.getNeighborPairs(pairs);
                    }
                    for(Pair p : pairs)
                    {
                        Part p1 = p.p1;
                        Part p2 = p.p2;
                        Vec2D center = new Vec2D((p1.getPosition().getX() + p2.getPosition().getX())/2, (p1.getPosition().getY() + p2.getPosition().getY())/2);
                        double avgSize = getVec2DProp("Size").getMagnitude();
                        double distance = new Vec2D(center.getX() - pos.getX(), center.getY() - pos.getY()).getMagnitude();
                        double k = 1;
                        double a = distance/avgSize;
                        double b = 1 - magnitude/25;
                        double c = (1 - (b + a)/2) * k;
                        
                        if(c > 0.1 && parts.contains(p1) && parts.contains(p2) && p1.getVec2DProp("Size").getX() > 1 && p1.getVec2DProp("Size").getY() > 1 && p2.getVec2DProp("Size").getX() > 1 && p2.getVec2DProp("Size").getY() > 1)
                        {
                            Weld w = new Weld();
                            w.setProp("Part1", p1, true);
                            w.setProp("Part2", p2, true);
                            w.setProp("Anchor", center, true);
                            w.setParent(Main.getActiveWorld().getService("JointsService"));
                        }
                    }*/
                } catch (WorldsException ex) {
                    ex.printStackTrace();
                }
            }

        });
    }
    public void setTargetPoint(Vec2D v)
    {
        try {
            setTempFixed(true);
            movePosition = 0;
            isMoving = true;
            if(targetPoint != null)
                startPosition = targetPoint;
            else
                startPosition = getPosition();
            targetPoint = v;
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }
    }
    public void wakeUpConnected()
    {
        for(Joint j : joints)
        {
            j.wakeupParts();
        }
    }
    public void drawLabel(Graphics g, Vec2D pos) throws WorldsException
    {

    }
    private Vec2D getCenterOfMass(Vec2D worldOrigin, Part[] parts)
    {
        double mass = 0;
        double invMass = 0;
        Vec2D center = new Vec2D(0, 0);
        for (Part p : parts) {
            Shape s = p.getShape();
            MassData massData = new MassData();
            if(s instanceof PolygonShape)
                ((PolygonShape)s).computeMass(massData);
            mass += massData.mass;
            center.x += massData.mass * massData.center.x;
            center.y += massData.mass * massData.center.y;
        }
        // Compute center of mass, and shift the origin to the COM
        invMass = 1.0f / mass;
        center.x *= invMass;
        center.y *= invMass;
        return center;
    }
    public void merge(final Part part)
    {
        Util.checkThread();
        if(getBody().parts.contains(part))
            return;

        BodyDef newbd = new BodyDef();
        bd.userData = this;

        Vec2D position = new Vec2D(0, 0);
        int i = 0;
        //Part[] parts = new Part[body.getParts().length + part.getBody().getParts().length];
        for(Part p : body.getParts())
        {
            //if(p.isConnected(this))
            {
                position.x += p.getPosition().x;
                position.y += p.getPosition().y;
                i++;
            }
            for (Joint j : (ArrayList<Joint>) p.joints.clone()) {
                if(j instanceof Hinge)
                {
                    j.setAnchor();
                }
            }
            //parts[j] = p;
        }
        for(Part p : part.getBody().getParts())
        {
            //if(p.isConnected(this))
            {
                position.x += p.getPosition().x;
                position.y += p.getPosition().y;
                //parts[j] = p;
                i++;
            }
            for (Joint j : (ArrayList<Joint>) p.joints.clone()) {
                if(j instanceof Hinge)
                {
                    j.setAnchor();
                }
            }
        }

        position = position.div(i);

        newbd.position = position;
        newbd.angle = 0;
        Body newbody = world.addBody(newbd);

        changeBody(newbody);
        part.changeBody(newbody);
        try
        {
            for(Part p : body.getParts())
            {
                for (Joint j : (ArrayList<Joint>) p.joints.clone()) {
                    if(j instanceof Hinge)
                    {
                        j.destroyJoint();
                        j.createJoint();
                    }
                }
            }
        }
        catch(WorldsException ex)
        {
            throw new RuntimeException(ex);
        }
    }
    public void changeBody(Body b)
    {
        if(body == b)
            return;
        ShapeDef newsd = null;
        if(this instanceof Box || this instanceof Triangle)
        {
            newsd = new PolygonDef();
            newsd.density = 1f;
            newsd.friction = 0.8f;
            newsd.restitution = 0.4f;
            for(Vec2D vert : ((PolygonShape)shape).getVertices())
            {
                Vec2D localPoint = b.getLocalPoint(getBody().getWorldPoint(vert));
                ((PolygonDef)newsd).addVertex(localPoint);
            }
        }
        else if(this instanceof Circle)
        {
            newsd = new CircleDef();
            newsd.density = 1f;
            newsd.friction = 0.8f;
            newsd.restitution = 0.4f;
            ((CircleDef)newsd).localPosition = b.getLocalPoint(getPosition());
            ((CircleDef)newsd).radius = ((CircleDef)sd).radius ;
        }
        setShapeDef(newsd);

        Shape newShape = b.createShape(newsd);
        Vec2D centroid = null;
        if(this instanceof Box || this instanceof Triangle)
        {
            centroid = ((PolygonShape)newShape).getCentroid();
            localOffset = new Vec2D(centroid);
        }
        else if(this instanceof Circle)
        {
            centroid = new Vec2D(0, 0);
            localOffset = new Vec2D(((CircleDef)newsd).localPosition);
        }
        rotationDiff = getBody().getAngle() + rotationDiff - b.getAngle();
        newShape.setUserData(this);

        b.setMassFromShapes();
        b.addPart(this);

        body.destroyShape(shape);
        body.removePart(this);
        body.setMassFromShapes();

        Body oldBody = body;

        setBody(b);
        setShape(newShape);

        for(Weld w : (ArrayList<Weld>)oldBody.welds.clone())
        {
            if(w.getPart1() == this || w.getPart2() == this)
            {
                b.addWeld(w);
                oldBody.removeWeld(w);
                w.localAnchor1 = new Vec2D(b.getLocalPoint(oldBody.getWorldPoint(w.localAnchor1)));
            }
        }

        b.wakeUp();
        b.resetFiltering();
        oldBody.wakeUp();
        oldBody.resetFiltering();
        boolean fixed = false;
        boolean fixedR = false;
        for(Part p : b.getParts())
        {
            try {
                if (p.getBooleanProp("Fixed") == true) {
                    fixed = true;
                }
                if(p.fixedRotation)
                {
                    fixedR = true;
                }
            } catch (WorldsException ex) {
                throw new RuntimeException(ex);
            }
        }
        for(Part p : oldBody.getParts())
        {
            if(p.isConnected(this))
            {
                p.changeBody(b);
            }
            try {
                if (p.getBooleanProp("Fixed") == true) {
                    fixed = true;
                }
                if(p.fixedRotation) {
                    fixedR = true;
                }
            } catch (WorldsException ex) {
                throw new RuntimeException(ex);
            }
        }
        try {
            setTempFixed(false);
        } catch (WorldsException ex) {
            throw new RuntimeException(ex);
        }
        try {
            if (getBooleanProp("Fixed") || fixed) {
                body.setFixed(true, false, true);
            }
            if(fixedR || fixedRotation) {
                body.setFixedRotation(true);
            }
        } catch (WorldsException ex) {
            throw new RuntimeException(ex);
        }
    }
    public void splitAway()
    {
        BodyDef newbd = new BodyDef();
        bd.userData = this;
        Body newbody = null;
        Vec2D position = getPosition();
        int i = 1;
        try
        {
            for(Part p : body.getParts())
            {
                if(p != this)
                {
                    position.x += p.getPosition().x;
                    position.y += p.getPosition().y;
                    i++;
                }
                for (Joint j : (ArrayList<Joint>) p.joints.clone()) {
                    if(j instanceof Hinge)
                    {
                        j.setAnchor();
                    }
                }
            }
            position = position.div(i);
            newbd.position = position;
            newbd.angle = body.getAngle() - (float) rotationDiff;
            newbody = world.addBody(newbd);
            changeBody(newbody);
            for(Part p : newbody.getParts())
            {
                for (Joint j : (ArrayList<Joint>) p.joints.clone()) {
                    if(j instanceof Hinge)
                    {
                        j.destroyJoint();
                        j.createJoint();
                    }
                }
            }
            newbody.wakeUp();
            newbody.resetFiltering();
        }
        catch(WorldsException ex)
        {
            throw new RuntimeException(ex);
        }
    }
    @Override
    public void beforeChangeWorld(net.worlds.World newWorld)
    {
        super.beforeChangeWorld(newWorld);
        try {
            Vec2D pos = getPosition();
            if (pos.x > newWorld.getWidth() || pos.y > newWorld.getHeight()) {
                setPosition(new Vec2D(10, 10));
            }
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }
    }
            @Override
    public void removeChild(GameObject c)
    {
        super.removeChild(c);
        try {
            if(getParent() instanceof AbstractCharacter)
            {
                AbstractCharacter ch = (AbstractCharacter) getParent();
                if((c instanceof VelocityController || c instanceof FixedRotation) && !ch.isDead)
                {
                        ch.onDied();
                }
            }
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }
    }
}
